iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 27
5

cookies

Cookie 和 Session 這兩個名詞,相信大部分的開發者都不會太陌生,特別是 Cookie,從社群網站、電商平台、Google Analytics 分析等地方,無處不見它的應用;但從本質上,Cookie & Session 究竟是什麼呢?這就要從 HTTP 的特性說起。

本系列文已經重新編校彙整編輯成冊,並正式出版囉!
《前端三十:從 HTML 到瀏覽器渲染的前端開發者必備心法》好評販售中!
喜歡我文章內容的讀者們,歡迎您 前往購買 支持!

無狀態的 HTTP

小明喜歡帶著 MAC 去星巴克當潮潮,每周總是會擠出時間去星巴克喝咖啡用電腦,常常一坐就是一整天;從最一開始不知道要點什麼,逐漸喝到辦了隨行卡,還成為金星級會員;而正妹店員也從詢問「先生貴姓」,轉變成讀取隨行卡後詢問「小明您的隨行卡餘額還有 280 元喔」。

注意到關鍵了嗎?對就是 正妹店員 隨行卡。在沒有隨行卡的情況下,店員不知道小明是誰,對店員來說,每一位客人都是單次的事件,,必須要透過隨行卡,才能知道小明是誰、剩下多少餘額,也就是小明在星巴克的「狀態」。

昨天 我們討論了 RESTful API 時,提到了 HTTP 是一個無狀態的通訊協定,就如同星巴克的店員不會記住客人是誰一樣,伺服器不會記住使用者是誰,而是把每一次收到的請求都視為獨立的行為。

如果是純粹展示靜態內容的網站,無狀態不會是個問題,反而是很棒的優點,因為客戶端、伺服器、資料庫都不需要儲存使用者狀態,也就省去了大量的儲存空間;但在複雜的網路應用,例如需要辨別使用者身分時,無狀態的通訊就會是個問題。

HTTP 的狀態管理機制

那如果要讓無狀態的 HTTP 能記住使用者是誰,該怎麼做呢?

還記得剛剛的隨行卡嗎?如果店家對每個使用者發出一個會員卡,就能夠透過會員卡來辨別使用者是誰了;同樣的道理,在 HTTP 的規範 - 狀態管理機制 的章節中,規範了一套讓無狀態的 HTTP 能得知使用者狀態的方法;簡單來說,就是伺服器透過 Header 的屬性 Set-Cookie,把使用者的狀態紀錄成儲存在使用者電腦裡的 Cookie,而瀏覽器在每一次發送請求時,都在 Header 中設定 Cookie 屬性,把 Cookie 帶上,伺服器就能藉由檢視 Cookie 的內容,得知瀏覽器使用者的狀態;而像是「從登入到登出」、「從開始瀏覽網頁到 Cookie 失效」,或是任何伺服器能認出使用者狀態的時間區間,就叫做 Session

例如瀏覽器回應的內容如下:

HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry

[page content]

而瀏覽器就會依照 Set-Cookie 的內容,建立、儲存指定的鍵值對(key-value pair);當瀏覽器要發送請求時,就會將 Cookies 帶上:

GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry

另外,前端開發者可以透過 document.cookie,在 JavaScript 中取得當前的 Cookies:

console.log(document.cookie) 
// yummy_cookie=choco; tasty_cookie=strawberry

關於更多規範提到的詳細內容,推薦可以參考 Huli 的淺談 Session 與 Cookie:一起來讀 RFC

Cookie 的屬性

Cookie 除了單純的鍵值對之外,伺服器也可以在 Set-Cookie 內標註這組資料的額外屬性:

  • domain:Cookie 的有效 domain,如果未設定,就會自動綁在執行 Set-Cookie 的 domain 下;雖然可以自行設置,但其實也只能在一級/次級網域之間調整,寫到其他人的 domain 是寫不進去的。
  • path:可以指定 Cookie 只在特定路徑下生效,未設定預設為 '/'
  • Max-Age:有效期限,單位是秒;當數值為正數時有效,負數時為本次 Session 有效;0 為刪除 Cookie
  • Expires:同上,只是指定的是時間點
  • secure:安全,當這個屬性被設為 true 時,此 Cookie 就只會在「安全的協議」下傳輸,通常為 HTTPS
  • HttpOnly:只能在網路傳輸中使用,當設為 true 時,此 Cookie 就無法在任何 JavaScript 程式碼中取得

由於有 domain 值的設定,Cookie 是有指定使用範圍的;僅會在請求的目標網域為 Cookie 設定的 domain 時被帶上;透過 domainpath 限縮 Cookie 的使用範圍,以及 Max-Age 指定有效期限,建立在 HTTP 這樣無狀態協議上的應用程式,也就得以獲取、控制使用者狀態了。

小提醒一下,有個容易讓人搞混的名詞,叫做「Session Cookies」,指的是沒有指定 ExpiresMax-Age 的 Cookies,當瀏覽器關閉時,這些 Cookies 也會跟著消失,故得名。

Cookie 的限制

由於 Cookie 是有大小數量限制的,單個最大 4K,一個 domain 下最多設置 20 個,不太可能將全部的使用者資料都儲存在瀏覽器端;同時,也因為對目標網域的每個請求都會帶上 Cookies,開發時千萬不要在 Cookie 中存入大量的資料,否則累計下來冗餘的資料傳輸非常可觀。

如果需要儲存更多的資料,或是省下每次傳輸都把 Cookie 帶好帶滿的流量浪費,就需要換成其他的實作方式了。實務上最常見的作法,是在 Cookie 中只存放能代表使用者是誰的 ID,而伺服器端則另外儲存每個 ID 的使用者狀態,當伺服器在收到請求時,藉由 Cookie 中的 ID,對應回使用者的狀態上,這樣就能認出使用者的身分了。

透過這樣簡單的機制,Cookie 的大小限制就不是問題了,但這種從 Cookie 中的 ID 換取狀態的模式,ID 如果被其他人獲取,使用者身分不就被他人使用了嗎?

對,確實有可能會被他人使用。但無論什麼方式,只要是需要知道使用者身分,使用者端自然會需要儲存資料;而只要是需要儲存資料,就都會有被盜取的風險。所以前面提到的 Cookie 屬性中才會有 secureHttpOnly,甚至實驗中的屬性 SameSite 等等,透過正確的設置它們,來降低潛在的風險,盡可能的保護 Cookie 的存取權限,並提高傳輸過程的安全性。

結語

Cookie 及 Session 是網頁應用不可或缺的機制之一,特別是在前端越來越複雜的現代網站上,如何在無狀態的 HTTP 上得知使用者的身分,並接續使用者狀態,是非常重要的一環;但儲存身分的同時,也就代表著身分被冒用的風險,開發者在應用上就需要特別留心注意了。

系列文來到了最後幾篇,內容也預計如同今天的內容,將涵蓋越來越大的範圍;請讀者們跟緊腳步,跟著筆者一起進行最後的收官,完成這趟每日變強之旅吧!

參考資料

筆者

Gary

半路出家網站工程師;半生熟的前端加上一點點的後端。
喜歡音樂,喜歡學習、分享,也喜歡當個遊戲宅。

相信一切安排都是最好的路。


上一篇
26. [BE] API 設計拿資料要透過 POST,會有什麼問題嗎?
下一篇
28. [WEB] HTTP 和 HTTPS 的差別是什麼?
系列文
前端三十 - 成為更好的前端工程師31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言